feat(action): local bindings + Output accessors in Actions#807
Merged
Conversation
Actions can now bind resources with current credentials and read resource Outputs, like a Cloudflare Worker. - ActionRuntimeContext: `yield* db.databaseId` inside an Action init captures the Output (→ dependency edge) and resolves it against the tracker at apply time. Works in both the inline and tagged `.make` forms. - Cloudflare.D1.QueryDatabaseLocal: a `*Local` layer (third variant alongside `*Binding`/`*Http`) that queries D1 over HTTP with the current credentials — no Worker host, no `host.bind`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
|
Install the packages built from this commit: alchemy bun add alchemy@https://pkg.ing/alchemy/1eef6b5@alchemy.run/better-auth bun add @alchemy.run/better-auth@https://pkg.ing/@alchemy.run/better-auth/1eef6b5@alchemy.run/pr-package bun add @alchemy.run/pr-package@https://pkg.ing/@alchemy.run/pr-package/1eef6b5 |
*Local layers (current-credentials, over HTTP) for the KV, R2, and
Queues bindings so they can be used inside an Action. Each refactors
its shared *Http client builder to accept an injectable auth
({ authorize, accountId }) reused by both the token-scoped Http variant
and the current-creds Local variant.
- KV: ReadNamespaceLocal / WriteNamespaceLocal / ReadWriteNamespaceLocal
- R2: ReadBucketLocal / WriteBucketLocal / ReadWriteBucketLocal
- Queues: WriteQueueLocal
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…h, Flagship, Browser
Extends the *Local (current-credentials, HTTP) binding pattern to every
Cloudflare capability with an HTTP data-plane, so they can be used inside
an Action:
- DNS: ReadDnsLocal / WriteDnsLocal / ReadWriteDnsLocal
- Vectorize: SearchIndexLocal
- Tunnel: ReadTunnelLocal / WriteTunnelLocal / ReadWriteTunnelLocal
- AI Search: QuerySearchLocal / QuerySearchNamespaceLocal
- Flagship: ReadFlagsLocal
- Workers: BrowserLocal (Browser Rendering REST API)
Each reuses/refactors its *Http client builder via an injectable
{ authorize, accountId } auth (shared by token-scoped Http and
current-creds Local), or a direct HTTP client where no *Http existed.
Every layer is live-tested against real Cloudflare.
Worker-runtime-only bindings have no Local variant and are intentionally
excluded: SecretsStore (write-only values), AnalyticsEngine (write-only
ingestion), Images (in-Worker transforms), Hyperdrive (pooler), Email
(send_email), Addressing, Artifacts, WorkersForPlatforms dispatch, AI
QueryGateway, RateLimit, VersionMetadata, service bindings.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…mData shim Now that distilled models the vectorize insert/upsert multipart part as `vectors`, SearchIndexLocal calls `vectorize.insertIndex`/`upsertIndex` directly instead of hand-building a FormData request. Bumps the distilled submodule to the regenerated service. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rm .make - Add "Binding resources" section: bind a resource inside an Action via its *Local layer (current-credentials HTTP), with the D1 seed example. - Add "Reading a resource's Outputs": `yield* resource.attr` accessors resolved at apply time + the dependency edge they create. - Fix the tagged form example: use the value form (interface + const), not the non-constructable `class extends` shape. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ocal Distilled now models D1 query/raw `params` as `unknown[]`, so the Local shim forwards native bind values (number/null/string) instead of failing schema encoding. Mirror the native binding's normalization the raw HTTP API skips: booleans -> 1/0, ArrayBuffer/views -> byte arrays (BLOB). Adds single + batch coverage for numeric/null/boolean binds. Bumps the distilled submodule to the params fix. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Verifies the ArrayBuffer/view -> byte-array normalization round-trips: binding a Uint8Array and SELECT length(?) returns the blob byte count. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rebases the D1 params + Vectorize multipart fixes onto distilled main (companion PR alchemy-run/distilled#379), dropping the stale claude/typescript-7-stable base so the submodule pointer no longer conflicts with alchemy main. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s-local-bindings-825ac1 # Conflicts: # distilled
Repoints the submodule to distilled 7d06469fc (companion PR alchemy-run/distilled#379): D1 params are now a precise value union `string | number | null | number[]` instead of unknown[]. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Distilled now models vectorize insert/upsert as a raw application/x-ndjson body (companion PR alchemy-run/distilled#379) matching the Cloudflare SDK, instead of the multipart-with-vectors workaround. SearchIndexLocal passes the ndjson blob as `body`. Bumps the distilled submodule. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Move the SearchIndexClient construction + shape adapters into unexported
SearchIndexHttpClient.ts, parameterized by an injectable SearchIndexAuth
({ authorize, accountId }) — same convention as KV/R2/Tunnel/AI. A future
token-scoped SearchIndexHttp layer reuses the builder; SearchIndexLocal is
now just the layer + auth.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…gship, Browser
Same convention as KV/R2/Tunnel/AI/Vectorize: move client construction into
unexported {Cap}HttpClient.ts scaffolding parameterized by an injectable
auth ({ authorize, accountId }), so a future token-scoped *Http layer reuses
the builder instead of duplicating it. The *Local layers are now just
layer + auth + deferred id accessor.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…head # Conflicts: # distilled
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Actions can now bind resources with the current credentials and read resource Outputs, the same way a
Cloudflare.Workerdoes.*Localbinding layersA third binding variant alongside
*Binding(native Worker) and*Http(scoped token). A*Locallayer resolves the current CLI credentials (ambient during stack-eval via the stack'sproviderscontext), never touches a Worker host orhost.bind, and returns the same client backed by the provider's HTTP API. Where an*Httpvariant already exists, its client builder is refactored to take an injectable{ authorize, accountId }auth shared by both the token-scoped Http and the current-creds Local; otherwise the Local layer implements a direct HTTP client (D1's shim is the reference).Implemented for every Cloudflare capability with an HTTP data-plane — each live-tested against real Cloudflare:
QueryDatabaseLocalReadNamespaceLocal/WriteNamespaceLocal/ReadWriteNamespaceLocalReadBucketLocal/WriteBucketLocal/ReadWriteBucketLocalWriteQueueLocalReadDnsLocal/WriteDnsLocal/ReadWriteDnsLocalSearchIndexLocalReadTunnelLocal/WriteTunnelLocal/ReadWriteTunnelLocalQuerySearchLocal/QuerySearchNamespaceLocalReadFlagsLocalBrowserLocal(Browser Rendering REST API)Worker-runtime-only bindings have no Local variant and are intentionally excluded (no account HTTP data-plane): SecretsStore (write-only secret values), AnalyticsEngine (write-only ingestion), Images (in-Worker transforms), Hyperdrive (connection pooler), Email (
send_email), Addressing, Artifacts, WorkersForPlatforms dispatch, AI QueryGateway (inference), RateLimit, VersionMetadata, and service bindings.Output accessors in Actions
An Action's init runs at stack-eval (before resources exist); its body runs at apply. To make
yield* db.databaseIdwork,RuntimeContextis split into two cooperating halves:Captured Outputs also become dependency edges, so the Action waits for the bound resource. Works in both the inline form and the tagged
.makeform.Verification
test/action.test.ts— inline + tagged.makecapture/accessor tests.*Localtest per implemented service (deploy resource + Action driving the Local binding).bun tsc -bclean.Follow-up
Vectorize
insertIndex/upsertIndexare generated with a multipart part namedbody, but the v2 API requires a part namedvectors;SearchIndexLocalworks around it in-layer. A distilled generator/spec fix would let every consumer get the correct multipart field for free.